﻿using System;
using System.Linq;
using System.Threading;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using Microsoft.Devices;

namespace ElectricityMate
{
    /// <summary>
    /// WriteableBitmap extension methods
    /// </summary>
    public static class Extensions
    {
        /// <summary>
        /// Helper method to crop bitmap image
        /// </summary>
        /// <param name="bmp">original bitmap</param>
        /// <param name="x">x</param>
        /// <param name="y">y</param>
        /// <param name="width">width</param>
        /// <param name="height">height</param>
        /// <param name="destination">destination bitmap</param>
        /// <returns>cropped bitmap</returns>
        public static WriteableBitmap Crop(this WriteableBitmap bmp, int x, int y, int width, int height, ref WriteableBitmap destination)
        {
            var SizeOfArgb = 4;
            var srcWidth = bmp.PixelWidth;
            var srcHeight = bmp.PixelHeight;

            // If the rectangle is completly out of the bitmap
            if (x > srcWidth || y > srcHeight)
            {
                return destination;
            }

            // Clamp to boundaries
            if (x < 0) x = 0;
            if (x + width > srcWidth) width = srcWidth - x;
            if (y < 0) y = 0;
            if (y + height > srcHeight) height = srcHeight - y;

            // Copy the pixels line by line using fast BlockCopy
            //var result = new WriteableBitmap(width, height);
            var result = destination;
            for (var line = 0; line < height; line++)
            {
                var srcOff = ((y + line) * srcWidth + x) * SizeOfArgb;
                var dstOff = line * width * SizeOfArgb;
                Buffer.BlockCopy(bmp.Pixels, srcOff, result.Pixels, dstOff, width * SizeOfArgb);
            }
            return result;
        }
    }

    public class LedDetector
    {
        private volatile bool _started = false;
        private WriteableBitmap _detectionFrame = new WriteableBitmap(100, 100);
        private WriteableBitmap _luminanceFrame = new WriteableBitmap(640, 480);
        public int Peaks { get; set; }
        public TimeSpan ElapsedTime { get; set; }
        public int FPS { get; set; }
        private PhotoCamera _camera;
        private byte[] _luminanceData;

        private Thread _frameThread;

        private Dispatcher _dispatcher;

        // call this event after every 30th frame
        public event EventHandler FrameAnalysed;

        /// <summary>
        /// Start led decetor
        /// </summary>
        public void Start(PhotoCamera camera, Dispatcher dispatcher)
        {
            if (_started)
            {
                return;
            }
            _started = true;
            _camera = camera;
            Peaks = 0;
            ElapsedTime = TimeSpan.Zero;
            // init luminance data array 
            _luminanceData = new byte[(int)_camera.PreviewResolution.Width * (int)_camera.PreviewResolution.Height];

            _dispatcher = dispatcher;

            // init and start thread to handle frames
            _frameThread = new Thread(PumpFrames);
            _frameThread.Start();
        }

        /// <summary>
        /// Stop led decetor
        /// </summary>
        public void Stop()
        {
            if (!_started)
            {
                return;
            }
            _started = false;
        }

        /// <summary>
        /// Read camera preview and detect led flashes
        /// </summary>
        private void PumpFrames()
        {
            var start = DateTime.Now;
            var prevAv = 0.0;
            var prevDelta = 0.0;
            var prevTimestamp = -1.0;
            var frameCounter = 0;
            while (_started)
            {
                try
                {
                    // get luminance buffer
                    _camera.GetPreviewBufferY(_luminanceData);

                    // use dispatcher or cross-thread excpetions!
                    _dispatcher.BeginInvoke(delegate
                        {
                            // create luminance bitmap
                            _luminanceData.CopyTo(_luminanceFrame.Pixels, 0);
                            // crop detection frame
                            _luminanceFrame.Crop(270, 190, 100, 100, ref _detectionFrame);
                            // calculate average luminance
                            var av = _detectionFrame.Pixels.Average(x => x);

                            // detect led flash peaks

                            if (frameCounter < 1)
                            {
                                frameCounter++;
                                prevAv = av;
                            }
                            else if (frameCounter < 2)
                            {
                                frameCounter++;
                                prevDelta = av - prevAv;
                                prevAv = av;
                            }
                            else
                            {
                                frameCounter++;
                                var delta = av - prevAv;

                                // TODO: good value for noise filter (10 seems ok)
                                var peak = 0;
                                if (Math.Abs(delta) < 10)
                                {
                                    peak = 0;
                                }
                                else
                                {
                                    var deltaSign = delta < 0 ? -1 : 1;
                                    var prevDeltaSign = prevDelta < 0 ? -1 : 1;

                                    if (deltaSign < prevDeltaSign)
                                    {
                                        peak = 1;
                                        Peaks++;
                                    }
                                    else if (deltaSign > prevDeltaSign)
                                    {
                                        peak = -1;
                                    }
                                }
                                prevDelta = delta;
                                prevAv = av;
                                System.Diagnostics.Debug.WriteLine("delta: " + Math.Round(delta) + " peak: " + peak);
                            }

                            ElapsedTime = (DateTime.Now - start);

                            FPS = (int)Math.Round(frameCounter / ElapsedTime.TotalSeconds);
                            if (frameCounter % 30 == 0)
                            {
                                if (FrameAnalysed != null) FrameAnalysed(this, EventArgs.Empty);
                            }
                        });
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine(ex.Message);
                    throw;
                }
            }
        }

        public bool IsStarted()
        {
            return _started;
        }

        /// <summary>
        /// Calculate effective power consumption.
        /// </summary>
        /// <returns>power consumption in kilowatts</returns>
        public double GetPowerConsumption()
        {
            var a = Peaks / (double)App.ViewModel.FlashesPerKWh;
            var b = ElapsedTime.TotalSeconds / 3600.0;
            return a / b;
        }
    }
}